/*
Copyright 2022.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers

import (
	"context"
	cachev1alpha1 "gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/observability/logs"
	"gitverse.ru/synapse/kubelatte/pkg/operator"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	"sync"
	"time"
)

// TriggerInstanceReconciler reconciles a TriggerInstance object
type TriggerInstanceReconciler struct {
	client.Client
	Scheme *runtime.Scheme
	once   sync.Once

	triggerObj                cachev1alpha1.TriggerInstance
	deletedTriggerInstanceObj cachev1alpha1.TriggerInstance
	initedFlag                bool
}

func (r *TriggerInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	operator.Mux.Lock()
	defer operator.Mux.Unlock()

	r.once.Do(func() {
		r.initedFlag = true
	})

	// Check delete event
	err := r.Get(ctx, req.NamespacedName, &r.triggerObj)
	if err != nil {
		if errors.IsNotFound(err) {
			if err = r.DeleteEventHndl(req); err != nil {
				return ctrl.Result{
					Requeue:      true,
					RequeueAfter: time.Second,
				}, err
			}
			return ctrl.Result{}, nil
		}
		return ctrl.Result{
			Requeue:      true,
			RequeueAfter: time.Second,
		}, err
	}

	// Add or update event
	err = r.AddOrUpdateEventHndl(ctx, req)
	if err != nil {
		return ctrl.Result{
			Requeue:      true,
			RequeueAfter: time.Second,
		}, err
	}

	return ctrl.Result{}, nil
}

func (r *TriggerInstanceReconciler) manageEvents() predicate.Predicate {
	return predicate.Funcs{
		CreateFunc: func(e event.CreateEvent) bool {
			operator.EventOperation = operator.AddEvent
			obj := *e.Object.(*cachev1alpha1.TriggerInstance)
			logs.Debugf("Create TI with name %s", obj.Name)
			if operator.IsCreationInClusterMode() && !CheckNamespace(obj.Namespace, operator.NSSelectorTI) {
				return false
			}
			return true
		},
		UpdateFunc: func(e event.UpdateEvent) bool {
			if e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() {
				operator.EventOperation = operator.UpdateEvent
				obj := *e.ObjectNew.(*cachev1alpha1.TriggerInstance)
				logs.Debugf("Update TI with name %s", obj.Name)
				if operator.IsCreationInClusterMode() && !CheckNamespace(obj.Namespace, operator.NSSelectorTI) {
					return false
				}
				return true
			}
			return false
		},
		DeleteFunc: func(e event.DeleteEvent) bool {
			operator.EventOperation = operator.DeleteEvent
			r.deletedTriggerInstanceObj = *e.Object.(*cachev1alpha1.TriggerInstance)
			logs.Debugf("Delete TI with name %s", r.deletedTriggerInstanceObj.Name)
			if operator.IsCreationInClusterMode() && !CheckNamespace(r.deletedTriggerInstanceObj.Namespace, operator.NSSelectorTI) {
				return false
			}
			return true
		},
	}
}

// SetupWithManager sets up the controller with the Manager.
func (r *TriggerInstanceReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&cachev1alpha1.TriggerInstance{}).
		WithEventFilter(r.manageEvents()).
		Complete(r)
}

func (r *TriggerInstanceReconciler) ProceedInstance(triggerInstanceObj cachev1alpha1.TriggerInstance) error {
	err := util.SendEventWithErr(operator.FactoryCreateRequestEvent, triggerInstanceObj)
	if err != nil {
		return err
	}
	return nil
}

func (r *TriggerInstanceReconciler) AddOrUpdateEventHndl(ctx context.Context, req ctrl.Request) error {
	var triggerInstanceObj cachev1alpha1.TriggerInstance
	logs.Debugf("TriggerInstanceController AddOrUpdateEventHndl name %s, send event", req.NamespacedName)
	err := r.Get(ctx, req.NamespacedName, &triggerInstanceObj)
	if err != nil {
		logs.Debugf("TriggerInstanceController AddOrUpdateEventHndl name %s, err %s", req.NamespacedName, err.Error())
		return err
	}
	return r.ProceedInstance(triggerInstanceObj)
}

func (r *TriggerInstanceReconciler) DeleteEventHndl(req ctrl.Request) error {
	logs.Debugf("TriggerInstanceController DeleteEventHndl name %s, send event", req.NamespacedName)
	err := util.SendEventWithErr(operator.WatcherRemoveObjectEvent, r.deletedTriggerInstanceObj)
	return err
}

func (r *TriggerInstanceReconciler) HealthCheck() bool {
	return r.initedFlag
}

func HandleUnhandledTIs(ns string) {
	if !operator.IsCreatorRole() {
		return
	}

	triggerInstances, err := operator.Cl.TriggerInstance().List(context.Background(), ns, metav1.ListOptions{})
	if err != nil {
		logs.Errorf("[CheckUnhandledTIs] Failed List triggerInstances %s", err)
		return
	}

	for _, ti := range triggerInstances.Items {
		if len(ti.Status.ResourceStatus) != 0 { // if ti was handled already, skip it
			continue
		}
		logs.Debugf("TI %v was not handled, start handling it", ti.Name)
		err := util.SendEventWithErr(operator.FactoryCreateRequestEvent, ti)
		if err != nil {
			logs.Errorf("Error during processing unhandled trigger instance %v: %v", ti.Name, err)
		}
	}
	return
}
